package calao;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.URL;
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.dev.GWTCompiler;
import com.google.gwt.dev.cfg.ModuleDef;
import java.util.Properties;
import java.util.Vector;

import com.google.gwt.dev.cfg.ModuleDefLoader;

import calao.client.DesktopProperty;
import calao.util.xml.XmlWriter;

public class DesktopCompiler 
{

	enum Status { NEW, RUNNING, COMPLETE, ERROR };

	static final String GWT_MODULE_EXTENSION = ".gwt.xml";
	
	final GWTCompiler gwtCompiler = new GWTCompiler();

	final String desktopName;
	final File desktopDefFile;

	File outDir = new File("gwt");
	Status status = Status.NEW;
	Properties desktopProperties = new Properties();

	TreeLogger logger = new TreeLogger() {
		public TreeLogger branch(Type type, String msg, Throwable caught) {
			if (isLoggable(type)) logError(msg, caught);
			return this;
		}
		public boolean isLoggable(Type type) {
			return type==TreeLogger.ERROR;
		}
		public void log(Type type, String msg, Throwable caught) {
			if (isLoggable(type)) logError(msg, caught);
		}
	};

	class CompileTask implements Runnable {
		public void run() {
			status = Status.RUNNING;
			try {
				log("Compiling desktop %s", desktopName);
				String module = desktopName;
				File bundleDir = new File(outDir, module);
				if (bundleDir.isDirectory()) {
					for (File file : bundleDir.listFiles()) file.delete();
				}
				ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger, module);
				gwtCompiler.setOutDir(outDir);
				gwtCompiler.distill(logger, moduleDef);
				String[] locales = moduleDef.getProperties().find("locale").getKnownValues();
				setDesktopProperty(DesktopProperty.LOCALES, filterOut(locales, "default"));
				setDesktopProperty(DesktopProperty.BUILD_TIME, ""+System.currentTimeMillis());
				createDesktopPropertiesFile(bundleDir);
				status = Status.COMPLETE;
				log("Desktop %s compiled", desktopName);
			} 
			catch (Exception e) {
				abort(e);
			}
		}
	}
	
	public DesktopCompiler(String desktopName, String[] modules)
	throws IOException
	{
		this.desktopName = desktopName;
		setDesktopProperty(DesktopProperty.NAME, desktopName);
		// create bundle module definition file
		this.desktopDefFile = File.createTempFile("calao", GWT_MODULE_EXTENSION);
		XmlWriter writer = new XmlWriter(new FileWriter(desktopDefFile));
		writer.startElement("module");
		for (String modName : modules) {
			writer.writeElement("inherits", "name", modName);
		}
		writer.endElement();
		writer.close();
	}

	public DesktopCompiler(String desktopName)
	{
		this.desktopName = desktopName;
		this.desktopDefFile = null; // rely on classpath
		setDesktopProperty("name", desktopName);
	}

	public DesktopCompiler()
	{
		this(Server.defaultDesktop);
	}

	public synchronized void compile()
	{
		CompileTask task = new CompileTask();
		try {
			// if we use a generated bundle module file
			// create a new class loader to give access to this file
			if (desktopDefFile!=null) {
				final String modulePath = 
					desktopName.replace('.', '/') + GWT_MODULE_EXTENSION;
				ClassLoader classLoader = 
					new ClassLoader(Thread.currentThread().getContextClassLoader()) 
				{
					@Override
					public URL getResource(String path) {
						if (path.equals(modulePath)) {
							try {
								return desktopDefFile.toURI().toURL();
							}
							catch (Exception e) { }
						}
						return super.getResource(path);
					}
				};
				// compile in separate thread
				Thread thread = new Thread(task);
				thread.setContextClassLoader(classLoader);
				thread.start();
				thread.join();
			}	
			else task.run();
		}
		catch (Exception e) {			
			abort(e);
		}
		finally {
			if (desktopDefFile!=null) desktopDefFile.delete();
		}
	}

	void logError(String msg, Throwable caught)
	{
		System.err.println(msg);
		if (caught!=null) caught.printStackTrace();
	}

	public void setOutDir(File dir)
	{
		this.outDir = dir;
	}
	
	public static void main(String[] argv) {
		for (String arg : argv) {
			DesktopCompiler compiler = new DesktopCompiler(arg);
			compiler.compile();
		}
	}
		
	void setDesktopProperty(String name, String... values)
	{
		StringBuffer list = null;
		for (String value : values) {
			if (list==null) list = new StringBuffer();
			else list.append(",");
			list.append(value);
		}
		if (list!=null) desktopProperties.setProperty(name, list.toString());
	}
	
	String[] filterOut(String[] values, String filteredValue)
	{
		Vector<String> result = new Vector<String>();
		for (String value : values) {
			if (value!=null && !value.equals(filteredValue)) result.add(value);
		}
		return result.toArray(new String[result.size()]);
	}
	
	void createDesktopPropertiesFile(File dir)
	throws IOException
	{
		FileOutputStream stream = new FileOutputStream(new File(dir, "desktop.properties"));
		desktopProperties.store(stream, "Generated by Calao Desktop Compiler");
		stream.close();
	}
	
	protected void log(String message, Object... args)
	{
		System.out.format(message, args);
		System.out.println();
		System.out.flush();
	}	
		
	protected void logError(String message, Object... args)
	{
		System.err.format(message, args);
		System.err.println();
		System.err.flush();
	}	
	
	protected void log(Exception e)
	{
		e.printStackTrace();
	}
	
	private void abort(Exception e)
	{
		status = Status.ERROR;
		log("Cannot compile desktop %s", desktopName);
		log(e);
	}

}
